package com.hongji.lms5xxcommunicator.service.imp;

import com.hongji.lms5xxcommunicator.entity.SnapshotCache;
import com.hongji.lms5xxcommunicator.entity.ScanPointEntity;
import com.hongji.lms5xxcommunicator.grid.RasterGrid;
import com.hongji.lms5xxcommunicator.grid.buff.InMemoryPointBuffer;
import com.hongji.lms5xxcommunicator.grid.config.RasterGridHolder;
import com.hongji.lms5xxcommunicator.grid.export.GridDeltaPayload;
import com.hongji.lms5xxcommunicator.realtime.PointDtoMapper;
import com.hongji.lms5xxcommunicator.realtime.WsTransferService;
import com.hongji.lms5xxcommunicator.realtime.websoket.SpringWsBroadcaster;
import com.hongji.lms5xxcommunicator.scanner.ScanFrame;
import com.hongji.lms5xxcommunicator.scanner.ScanStrategy;
import com.hongji.lms5xxcommunicator.scanner.ScanStrategyFactory;
import com.hongji.lms5xxcommunicator.service.ScanPointDataService;
import com.hongji.lms5xxcommunicator.service.UnifiedScanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Service
public class UnifiedScanServiceImp implements UnifiedScanService {

    @Autowired private ScanStrategyFactory strategyFactory;
    private volatile ScanStrategy strategy;

    @Autowired private InMemoryPointBuffer pointBuffer;
    @Autowired private RasterGridHolder rasterGridHolder;
    @Autowired private SpringWsBroadcaster wsBroadcaster;
    @Autowired private SnapshotCache snapshotCache;
    @Autowired private ScanPointDataService scanPointDataService;

    @Value("${ws.batch_size:5000}") private int wsBatchSize;
    @Value("${front.coord.y.base:2300}") private double viewYBase;
    @Value("${front.coord.z.offset:400}") private double viewZOffset;
    @Value("${grid.export.row-origin:bottom}") private String rowOrigin;
    private boolean isBottomOrigin() { return !"top".equalsIgnoreCase(rowOrigin); }

    private volatile WsTransferService wsTransfer;
    private volatile PointDtoMapper pointDtoMapper;

    // DB flush
    @Value("${db.flush.enabled:true}") private boolean dbFlushEnabled;
    @Value("${db.flush.batch:5000}") private int dbFlushBatch;
    @Value("${db.flush.interval.ms:1000}") private long dbFlushIntervalMs;
    private ScheduledExecutorService dbExec;

    // 运行态
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final ArrayBlockingQueue<ScanFrame> frameQ = new ArrayBlockingQueue<>(64);
    private final ExecutorService procExec = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r, "scan-frame-processor");
        t.setDaemon(true);
        return t;
    });

    @Override
    public void start() {
        ensureFrontendHelpers();
        if (!running.compareAndSet(false, true)) { log.warn("scan already running"); return; }
        try {
            strategy = strategyFactory.resolve();
            if (Objects.isNull(strategy)) {
                log.info("未找到扫描仪，请确认配置项");
                return;
            }
            strategy.start(this::onFrameArrived);
            procExec.submit(this::processLoop);
            startDbFlusher();
            log.info("UnifiedScanService started with {}", strategy.getClass().getSimpleName());
        } catch (Exception e) {
            log.error("start failed", e);
            stop();
        }
    }

    @Override
    public void stop() {
        if (!running.getAndSet(false)) return;
        try { if (strategy != null) strategy.stop(); } catch (Exception ignore) {}
        stopDbFlusher();
        frameQ.clear();
        log.info("UnifiedScanService stopped");
    }

    @PreDestroy
    public void shutdown() {
        try { stop(); } catch (Exception ignore) {}
    }

    private void ensureFrontendHelpers() {
        if (pointDtoMapper == null) pointDtoMapper = new PointDtoMapper(viewYBase, viewZOffset, isBottomOrigin());
        if (wsTransfer == null) wsTransfer = new WsTransferService(wsBroadcaster, snapshotCache, pointDtoMapper, wsBatchSize);
    }

    private void onFrameArrived(ScanFrame f) {
        if (f == null || f.size() <= 0) return;
        if (!frameQ.offer(f)) { frameQ.poll(); frameQ.offer(f); }
    }

    private void processLoop() {
        while (running.get()) {
            try {
                ScanFrame f = frameQ.poll(1000, TimeUnit.MILLISECONDS);
                if (f == null) continue;

                // 外部不再参与 X 计算：若 xs 缺失或长度不匹配，则丢弃
                if (f.xs == null || f.xs.length < f.size()) {
                    log.warn("drop frame: xs is null or size mismatch (xs={}, ys={}, zs={})",
                            (f.xs == null ? -1 : f.xs.length),
                            (f.ys == null ? -1 : f.ys.length),
                            (f.zs == null ? -1 : f.zs.length));
                    continue;
                }

                RasterGrid grid = rasterGridHolder.get();
                if (grid == null) { log.error("RasterGrid 未初始化，丢弃帧"); continue; }

                List<ScanPointEntity> entities = toEntities(f.xs, f.ys, f.zs);

                // 推流：网格内部会依据“当前会话网格”合并显示
                GridDeltaPayload delta = grid.mergeWithDelta(entities, null);
                wsTransfer.pushDelta(delta, grid);

                // 刷库：由 ScanPointDataService 统一量化 ix/iy + 快照清理
                pointBuffer.addAllOrReplace(entities);

            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            } catch (Throwable ex) {
                log.error("processLoop error", ex);
            }
        }
    }

    private static List<ScanPointEntity> toEntities(double[] xs, double[] ys, double[] zs) {
        int n = Math.min(xs != null ? xs.length : 0, Math.min(ys != null ? ys.length : 0, zs != null ? zs.length : 0));
        if (n <= 0) return Collections.emptyList();
        List<ScanPointEntity> list = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            ScanPointEntity e = new ScanPointEntity();
            e.setX(xs[i]); e.setY(ys[i]); e.setZ(zs[i]);
            list.add(e);
        }
        return list;
    }

    private void startDbFlusher() {
        if (!dbFlushEnabled) { log.info("db flusher disabled"); return; }
        if (dbExec != null && !dbExec.isShutdown()) return;
        dbExec = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "db-flusher");
            t.setDaemon(true);
            return t;
        });
        dbExec.scheduleAtFixedRate(this::flushOnce, dbFlushIntervalMs, dbFlushIntervalMs, TimeUnit.MILLISECONDS);
        log.info("db flusher started, interval={}ms, batch={}", dbFlushIntervalMs, dbFlushBatch);
    }

    private void stopDbFlusher() {
        if (dbExec != null) {
            dbExec.shutdownNow();
            dbExec = null;
            log.info("db flusher stopped");
        }
    }

    private void flushOnce() {
        try {
            List<ScanPointEntity> batch = pointBuffer.drainToList(dbFlushBatch);
            if (batch == null || batch.isEmpty()) return;
            int n = scanPointDataService.batchUpsertPoints(batch);
            log.info("db flush ok, rows={}", n);
        } catch (Exception e) {
            log.error("db flush failed", e);
        }
    }

    @Override
    public boolean isRunning() {
        return running.get();
    }
}
